package com.pisolution.scheduler.runtime.worker;

import com.pisolution.scheduler.core.utils.OSUtils;
import com.pisolution.scheduler.runtime.registry.HeartbeatTask;
import com.pisolution.scheduler.runtime.registry.RegistryCenter;

import com.google.common.collect.Sets;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 工作节点辅助注册工具.
 *
 * @author sora
 * @version 0.1.0
 */
@Service
public class WorkerRegistry {
    private static final Logger LOGGER = LoggerFactory.getLogger(WorkerRegistry.class);

    // -------------------------------------------------------------------------------------------------

    /** 用于工作节点的注册与取消注册. */
    private final RegistryCenter center;

    /** 用于读取工作节点注册相关配置. */
    private final WorkerConfig config;

    // -------------------------------------------------------------------------------------------------

    /** 用于定时上传工作节点心跳至注册中心. */
    private ScheduledExecutorService heartbeatService;

    /** 工作节点注册路径. */
    private String path;

    /** 当前工作节点注册时间. */
    private String startTime;

    // -------------------------------------------------------------------------------------------------
    //  初始化
    // -------------------------------------------------------------------------------------------------

    public WorkerRegistry(final RegistryCenter center, final WorkerConfig config) {
        this.center = center;
        this.config = config;
    }

    // -------------------------------------------------------------------------------------------------
    //  生命周期
    // -------------------------------------------------------------------------------------------------

    /**
     * 在依赖注入完成之后 读取配置文件中工作节点对外交互服务开放端口以初始化工作节点在注册中心的注册路径.
     *
     * <p><b>注意:</b></p>
     * <p>该方法会在构造函数与依赖注入之后自动调用 无需手动触发.</p>
     */
    @SuppressWarnings("unused")
    @PostConstruct
    public void start() {
        this.heartbeatService = Executors.newSingleThreadScheduledExecutor(
            new BasicThreadFactory.Builder().namingPattern("heartbeat-worker-").build()
        );
        this.path = String.format("/servers/worker/%s:%d", OSUtils.getHost(), this.config.getPort());
        this.startTime = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
    }

    // -------------------------------------------------------------------------------------------------
    //  节点注册
    // -------------------------------------------------------------------------------------------------

    /**
     * 将工作节点注册至注册中心并监听工作节点与注册中心之间的连接状态以响应特殊的连接状态 定期上传工作节点心跳信息以
     * 确认连接一直处于活跃状态.
     */
    public void register() {
        // 1. 获取当前主机网络地址.
        final String host = OSUtils.getHost();

        // 2. 在注册中心创建指定节点并监控与注册中心的连接状态.
        this.center.getClient().ephemeral(this.path, "");
        this.center.getClient().getClient().getConnectionStateListenable().addListener((client, state) -> {
            switch (state) {
                case LOST:
                    LOGGER.error("The worker server {} lost connection towards the registry center.", host);

                    break;
                case RECONNECTED:
                    LOGGER.info("The worker server {} reconnect to the registry center.", host);

                    this.center.getClient().ephemeral(this.path, "");
                    break;
                case SUSPENDED:
                    LOGGER.warn("The worker server {} suspend to interact with the registry center.", host);

                    break;
                default:
                    // 剩下的状态无需特别处理.

                    break;
            }
        });

        // 3. 定期上传自身心跳到注册路径.
        this.heartbeatService.scheduleAtFixedRate(
            new HeartbeatTask(
                this.startTime,
                this.config.getReservedMemory(),
                this.config.getMaxCPULoadAvg(),
                Sets.newHashSet(this.path),
                this.center
            ),
            this.config.getHeartbeatInterval(),
            this.config.getHeartbeatInterval(),
            TimeUnit.SECONDS
        );

        LOGGER.info("Succeed to register the worker server {} to the registry center.", host);
    }

    /**
     * 从注册中心移除该工作节点以取消注册.
     */
    public void unregister() {
        this.center.getClient().remove(this.path);
        this.heartbeatService.shutdownNow();

        LOGGER.info(
            "Succeed to unregister the worker server {} from the registry center.",
            this.path.substring(this.path.lastIndexOf("/") + 1)
        );
    }
}
